package mapEditor.map;

import debug.*;

import mapEditor.MapEditor;
import mapEditor.obj.DrawObject;
import mapEditor.obj.GroupObject;
import mapEditor.obj.ImageObject;
import mapEditor.utils.MapLocation;
import mapEditor.utils.Util;
import mapEditor.utils.ValueSpinner;

import org.json.JSONObject;
import org.json.JSONException;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class ObjectModel
{ private static final String TAG="mapEditor.obj.ObjectModel";
  private static final boolean VERBOSE=true;
  private static final boolean DEBUG=true;
  
  private static final String META_DATA_IDENT="METADATA";	
  private static final String BACKGROUND_IMAGE_IDENT="BACKGROUND";	

  private MapCanvas mapCanvas;
  public MapCanvas getMapCanvas() { return(mapCanvas); }
  
  private GroupObject rootGroup;
  private ImageObject backgroundImage;
  // TEMP
  public void setBackgroudImage(ImageObject image) { backgroundImage=image; }
  
  private GroupObject currentGroup;
  public void setCurrentGroup(GroupObject currentGroup) { this.currentGroup=currentGroup; }
  public GroupObject getCurrentGroup() { return(currentGroup); }
  
  private String name;
  public String getName() { return(name); }
  
  private double currentZoom=1;
  private double canvasWidth;  // In pixels
  private double canvasHeight; // In pixels
  private double mapGridOrigoEast=601150;   // X-Coordinate of Grid's Origo
  private double mapGridOrigoNorth=6645600; // Y-Coordinate of Grid's Origo
  private double gridOrigoX=48;  // Grid Origo's x in pixels
  private double gridOrigoY=163; // Grid Origo's y in pixels
  private double gridMeshWidthInPixels=325; // GridMesh size, x-direction, in pixels
  private double gridMeshHeightInPixels=325; // GridMesh size, y-direction, in pixels
  private double gridMeshWidthInMeter=50; // GridMesh size, x-direction, in meter
  private double gridMeshHeightInMeter=50; // GridMesh size, y-direction, in meter
  
  public double getCurrentZoom() { return(currentZoom); }
  public double getCanvasWidth() { return(canvasWidth*currentZoom); }
  public double getCanvasHeight() { return(canvasHeight*currentZoom); }
  public double getGridOrigoEast() { return(mapGridOrigoEast); }
  public double getGridOrigoNorth() { return(mapGridOrigoNorth); }
  public double getGridOrigoX() { return(gridOrigoX*currentZoom); }
  public double getGridOrigoY() { return(gridOrigoY*currentZoom); }
  private double getGridMeshWidthInPixels() { return(gridMeshWidthInPixels*currentZoom); }
  private double getGridMeshHeightInPixels() { return(gridMeshHeightInPixels*currentZoom); }
  public double xPixelInMeter() { return(gridMeshWidthInMeter/getGridMeshWidthInPixels()); }  // E.G. 1 pixel == 50/325 meter 
  public double yPixelInMeter() { return(gridMeshHeightInMeter/getGridMeshHeightInPixels()); }  // E.G. 1 pixel == 50/325 meter 
  public double xMetersToPixel() { return(getGridMeshWidthInPixels()/gridMeshWidthInMeter); } // E.G. 1 meter == 325/50 pixels
  public double yMetersToPixel() { return(getGridMeshHeightInPixels()/gridMeshHeightInMeter); } // E.G. 1 meter == 325/50 pixels
  
  // ******************************************************************
  // *** CONSTRUCTOR: ObjectModel -- Create Empty ObjectModel
  // ******************************************************************
  public ObjectModel(String name,MapCanvas mapCanvas,URL backgroundImageUrl) throws IOException,JSONException
  { this.name=name;  
    this.mapCanvas=mapCanvas;
    Toolkit toolkit=Toolkit.getDefaultToolkit();
    Image background=toolkit.getImage(backgroundImageUrl);
    // Ensure background image loaded
    while( ((canvasWidth=background.getWidth(null))<0) || ((canvasHeight=background.getHeight(null))<0) ) 
    { try { Thread.sleep(10); } catch(InterruptedException ignore){} }	
    if(VERBOSE) Log.i(TAG,"<init> BACKGOUND IMAGE WIDTH="+canvasWidth+", HEIGHT="+canvasHeight);
	MapLocation p1=MapLocation.newPixelPoint(this,0,0);
    MapLocation p2=MapLocation.newPixelPoint(this,canvasWidth,canvasHeight);
    backgroundImage=new ImageObject(this,p1,p2,backgroundImageUrl.toString());
    backgroundImage.setIdent(BACKGROUND_IMAGE_IDENT);
    rootGroup=new GroupObject(this);
  }

  // ******************************************************************
  // *** CONSTRUCTOR: ObjectModel -- Create ObjectModel from URL
  // ******************************************************************
  public ObjectModel(String name,MapCanvas mapCanvas,Reader reader) throws IOException,JSONException
  { this.name=name;  
    this.mapCanvas=mapCanvas;
      
    StringBuffer buf=new StringBuffer();
    int c; while((c=reader.read())>=0) buf.append((char)c);
//	if(DEBUG) Log.d(TAG,"<init>: FILE="+buf);
    JSONObject rootObject=new JSONObject(buf.toString());
    readMetaData(rootObject.getJSONObject(META_DATA_IDENT));
    JSONObject jsonObject=rootObject.optJSONObject(BACKGROUND_IMAGE_IDENT);
    if(jsonObject!=null) backgroundImage=new ImageObject(this,jsonObject);
	rootGroup=new GroupObject(this,rootObject);
	if(VERBOSE) printJSON(TAG,"Create ObjectTree from URL: "+name,rootObject);
  }
  private void readMetaData(JSONObject metaData) throws JSONException
  { if(VERBOSE) printGridMetaData("readGridMetaData("+metaData+")");
    //if(metaData==null) return;
    canvasWidth=metaData.getDouble("Width");
    canvasHeight=metaData.getDouble("Height");
    mapGridOrigoEast=metaData.optDouble("GridOrigoEast",mapGridOrigoEast);
    mapGridOrigoNorth=metaData.optDouble("GridOrigoNorth",mapGridOrigoNorth);
    gridOrigoX=metaData.optDouble("GridOrigoX",gridOrigoX);
    gridOrigoY=metaData.optDouble("GridOrigoY",gridOrigoY);
    gridMeshWidthInPixels=metaData.optDouble("MeshWidthInPixels",gridMeshWidthInPixels);
    gridMeshHeightInPixels=metaData.optDouble("MeshHeightInPixels",gridMeshHeightInPixels);
    gridMeshWidthInMeter=metaData.optDouble("MeshWidthInMeter",gridMeshWidthInMeter);
    gridMeshHeightInMeter=metaData.optDouble("MeshHeightInMeter",gridMeshHeightInMeter);
    if(VERBOSE) printGridMetaData("AFTER readGridMetaData");
	if(DEBUG) Log.d(TAG,"<init>: canvasWidth="+canvasWidth+", canvasHeight="+canvasHeight);
  }
  
  //*******************************************************************
  // *** METHOD: addChild
  //*******************************************************************
  public void addChild(DrawObject child)
  { //if(currentGroup!=null)
    { currentGroup.addChild(child);
      MapEditor.repaint(); 
    }
  }
  
  //*******************************************************************
  // *** METHOD: removeChild
  //*******************************************************************
  public void removeChild(DrawObject child)
  { if(currentGroup!=null)
    { currentGroup.removeChild(child);
	  MapEditor.repaint(); 
    }
  }
  
  //*******************************************************************
  // *** METHOD: getObject
  //*******************************************************************
  public DrawObject getObject(MapLocation p)
  { if(rootGroup!=null) return(rootGroup.getObject(p));
	return(null);
  }

  //*******************************************************************
  // *** METHOD: zoom
  //*******************************************************************
  private static final double zoomFactor=1.2;
  public void zoom(boolean in)
  { double newZoom=(in)?currentZoom*zoomFactor:currentZoom/zoomFactor;
	currentZoom=newZoom;
  }
  public void zoomReset() { currentZoom=1; }
  

  //*******************************************************************
  // *** METHOD: toObjectTableModel
  //*******************************************************************
  public ObjectTableModel toObjectTableModel() { return(rootGroup.toObjectTableModel()); }

  // ******************************************************************
  // *** METHOD: paint
  // ******************************************************************
  public void paint(Graphics2D g,ImageObserver imageObserver)
  { rootGroup.paint(g,imageObserver); }
  
  // ******************************************************************
  // *** METHOD paintBackground
  // ******************************************************************
  public void paintBackground(final Graphics2D g,ImageObserver imageObserver)
  { backgroundImage.paint(g,imageObserver); }
  
  // ******************************************************************
  // *** METHOD paintGrid
  // ******************************************************************
  public void paintGrid(final Graphics2D g,ImageObserver imageObserver)
  { double width=canvasWidth*currentZoom;
	double height=canvasHeight*currentZoom;
	int n=(int)(canvasWidth/gridMeshWidthInPixels)+2;
	g.setColor(Color.red);
	g.draw(new Ellipse2D.Double(getGridOrigoX()-10,getGridOrigoY()-10,20,20));
	for(int i= -1;i<n;i++)
	{ double y=getGridOrigoY()+i*getGridMeshHeightInPixels(); 
	  if(y<height) g.draw(new Line2D.Double(0,y,width,y));
	}
	n=(int)(canvasHeight/gridMeshHeightInPixels)+2;
	g.setColor(Color.blue);
	for(int i= -1;i<n;i++)
	{ double x=getGridOrigoX()+i*getGridMeshWidthInPixels();
	  if(x<width) g.draw(new Line2D.Double(x,0,x,height));
	}
	g.setColor(Color.black);
  }

  //*******************************************************************
  // *** METHOD: printJSON
  //*******************************************************************
  private void printJSON(String tag,String title,JSONObject rootObject)
  {	Log.println("*** "+tag+": "+title+" ********************************************************************************");  
    try { Log.println(rootObject.toString(4)); } catch(JSONException e) {}
	Log.println("**********************************************************************************************");   
  }
  
  public void printGridMetaData(String title)
  { Log.println("*** "+title+" **********************************************************************************");
    Log.println("*** Width:  "+canvasWidth);
    Log.println("*** Height: "+canvasHeight);
    Log.println("*** GridOrigoEast:  "+mapGridOrigoEast);
    Log.println("*** GridOrigoNorth: "+mapGridOrigoNorth);
    Log.println("*** GridOrigoX: "+gridOrigoX);
    Log.println("*** GridOrigoY: "+gridOrigoY);
    Log.println("*** MeshWidthInPixels:  "+gridMeshWidthInPixels);
    Log.println("*** MeshHeightInPixels: "+gridMeshHeightInPixels);
    Log.println("*** MeshWidthInMeter:  "+gridMeshWidthInMeter);
    Log.println("*** MeshHeightInMeter: "+gridMeshHeightInMeter);
    Log.println("*** ********************************************************************************************");
  }
  
  //*******************************************************************
  // *** METHOD: writeJSONFile
  //*******************************************************************
  public void writeJSONFile(Writer writer) throws JSONException
  { JSONObject rootObject=rootGroup.toJSONObject();
    rootObject.put(META_DATA_IDENT,metaDataToJSONObject());
    rootObject.put(BACKGROUND_IMAGE_IDENT,backgroundImage.toJSONObject());
    if(VERBOSE) printJSON(TAG,"ObjectTree.writeJSONFile",rootObject);
    rootObject.write(writer);
  }
  
  private JSONObject metaDataToJSONObject() throws JSONException 
  { if(VERBOSE) printGridMetaData("writeGridMetaData");
    JSONObject metaData=new JSONObject();
    metaData.put("Width",canvasWidth);
    metaData.put("Height",canvasHeight);
	metaData.put("GridOrigoEast",mapGridOrigoEast);
    metaData.put("GridOrigoNorth",mapGridOrigoNorth);
    metaData.put("GridOrigoX",gridOrigoX);
    metaData.put("GridOrigoY",gridOrigoY);
    metaData.put("MeshWidthInPixels",gridMeshWidthInPixels);
    metaData.put("MeshHeightInPixels",gridMeshHeightInPixels);
    metaData.put("MeshWidthInMeter",gridMeshWidthInMeter);
    metaData.put("MeshHeightInMeter",gridMeshHeightInMeter);
    return(metaData);
  }
  
  //********************************************************************
  //*** INNER CLASS: GridCalibratingDialog
  //********************************************************************
  public class GridCalibratingDialog extends JDialog
  { static final long serialVersionUID=0; // AD'HOC
    static final double MINIMUM=0;
    static final double MAXIMUM=Integer.MAX_VALUE;

    private JButton okBtn;
    private JButton cancelBtn;

    // Initial Values:
    private double initialGridOrigoX;
    private double initialGridOrigoY;
    private double initialGridMeshWidthInPixels;
    private double initialGridMeshHeightInPixels;
    private double initialGridMeshWidthInMeter;
    private double initialGridMeshHeightInMeter;
    private double initialMapGridOrigoEast;
    private double initialMapGridOrigoNorth;
    private void saveInitialValues()
    { initialGridOrigoX=gridOrigoX;
      initialGridOrigoY=gridOrigoY;
      initialGridMeshWidthInPixels=gridMeshWidthInPixels;
      initialGridMeshHeightInPixels=gridMeshHeightInPixels;
      initialGridMeshWidthInMeter=gridMeshWidthInMeter;
      initialGridMeshHeightInMeter=gridMeshHeightInMeter;
      initialMapGridOrigoEast=mapGridOrigoEast;
      initialMapGridOrigoNorth=mapGridOrigoNorth;
    }
    private void restoreInitialValues()
    { gridOrigoX=initialGridOrigoX;    	  
 	  gridOrigoY=initialGridOrigoY;    	  
 	  gridMeshWidthInPixels=initialGridMeshWidthInPixels;    	  
 	  gridMeshHeightInPixels=initialGridMeshHeightInPixels;  
 	  gridMeshWidthInMeter=initialGridMeshWidthInMeter;    	  
 	  gridMeshHeightInMeter=initialGridMeshHeightInMeter;
 	  mapGridOrigoEast=initialMapGridOrigoEast;
 	  mapGridOrigoNorth=initialMapGridOrigoNorth;
 	  MapEditor.getCurrentMapCanvas().repaint();
    }

    // ******************************************************************
    // *** CONSTRUCTOR: GridCalibratingDialog
    // ******************************************************************
    public GridCalibratingDialog()
    { saveInitialValues();
      setBounds(300,200,370,350); // Popup to: x,y,width,height
      addWindowListener(new WindowAdapter()
      { public void windowClosing(WindowEvent we)
        { restoreInitialValues(); dispose(); }
      });
      setTitle("Coordinate System Calibration");
      JPanel center=new JPanel();
      center.add(getPixelPanel("Pixel Calibration"));
      center.add(new JLabel(" ")); // Space only
      center.add(getMeterPanel("Meter Calibration"));
      center.add(new JLabel(" ")); // Space only
      center.add(getLocationPanel("UTM32 Reference"));

      JPanel buttons=new JPanel();
      buttons.add(okBtn=new JButton("Ok"));
      okBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { dispose(); }
      });
      buttons.add(cancelBtn=new JButton("Cancel"));
      cancelBtn.addActionListener(new ActionListener()
      { public void actionPerformed(ActionEvent e)
        { restoreInitialValues(); dispose(); }
      });
      setLayout(new BorderLayout());
      add(new JLabel(" "),BorderLayout.NORTH); // Space only
      add(center, BorderLayout.CENTER);
      add(buttons, BorderLayout.SOUTH);
      setAlwaysOnTop(true);
      setVisible(true);
    }
    
    // ******************************************************************
    // *** PRIVATE: getPixelPanel
    // ******************************************************************
    private JPanel getPixelPanel(String title)
    { JPanel panel=new JPanel();
      panel.setBorder(Util.getTitleBorder(title));
      panel.setLayout(new GridLayout(4,2));
      double stepSize=0.5;
      panel.add(new JLabel("Horizontal Grid Size:"));
      panel.add(new ValueSpinner(initialGridMeshWidthInPixels,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridMeshWidthInPixels=value; }
      });
      panel.add(new JLabel("Vertical Grid Size:"));
      panel.add(new ValueSpinner(initialGridMeshHeightInPixels,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridMeshHeightInPixels=value; }
      });
      panel.add(new JLabel("Grid Origo's X-Axis:"));
      panel.add(new ValueSpinner(initialGridOrigoY,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridOrigoY=value; }
      });
      panel.add(new JLabel("Grid Origo's Y-Axis:"));
      panel.add(new ValueSpinner(initialGridOrigoX,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridOrigoX=value; }
      });
      return(panel);
    }

    // ******************************************************************
    // *** PRIVATE: getMeterPanel
    // ******************************************************************
    private JPanel getMeterPanel(String title)
    { JPanel panel=new JPanel();
      panel.setBorder(Util.getTitleBorder(title));
      panel.setLayout(new GridLayout(2,2));
      double stepSize=0.5;
      panel.add(new JLabel("Horizontal Grid Meter:"));
      panel.add(new ValueSpinner(initialGridMeshWidthInMeter,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridMeshWidthInMeter=value; }
      });
      panel.add(new JLabel("Vertical Grid Meter:"));
      panel.add(new ValueSpinner(initialGridMeshHeightInMeter,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { gridMeshHeightInMeter=value; }
      });
      return(panel);
    }
 
    // ******************************************************************
    // *** PRIVATE: getLocationPanel
    // ******************************************************************
    private JPanel getLocationPanel(String title)
    { JPanel panel=new JPanel();
      panel.setBorder(Util.getTitleBorder(title));
      panel.setLayout(new GridLayout(2,2));
      double stepSize=1;
      panel.add(new JLabel("Grid Origo's UTM32 East(X):"));
      panel.add(new ValueSpinner(initialMapGridOrigoEast,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { mapGridOrigoEast=value; }
      });
      panel.add(new JLabel("Grid Origo's UTM32 North(Y):"));
      panel.add(new ValueSpinner(initialMapGridOrigoNorth,MINIMUM,MAXIMUM,stepSize)
      { static final long serialVersionUID=0; // AD'HOC
        public void update(double value) { mapGridOrigoNorth=value; }
      });
      return(panel);
    }
  }  
  
    
}
